package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.PageResult;
import com.leyou.item.pojo.*;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.search.reposiroty.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {
    private static final Logger logger = LoggerFactory.getLogger(SearchService.class);
    private static final  ObjectMapper mapper = new ObjectMapper();

    @Autowired
    private BrandClient brandClient;
    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private SpecClient specClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate template;

    public Goods buildGoods(Spu spu) throws IOException {
        Goods goods = new Goods();

        // 查询商品分类名称
        List<String> names = this.categoryClient.queryNameByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        // 查询sku
        List<Sku> skus = this.goodsClient.querySkuBySpuId(spu.getId());
        // 查询详情
        SpuDetail spuDetail = this.goodsClient.querySpuDetailById(spu.getId());
        // 查询规格参数
        List<SpecParam> params = this.specClient.querySpecParams(null, spu.getCid3(), true, null);

        // 处理sku，仅封装id、价格、标题、图片，并获得价格集合
        List<Long> prices = new ArrayList<>();
        List<Map<String, Object>> skuList = new ArrayList<>();
        skus.forEach(sku -> {
            prices.add(sku.getPrice());
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", sku.getId());
            skuMap.put("title", sku.getTitle());
            skuMap.put("price", sku.getPrice());
            skuMap.put("image", StringUtils.isBlank(sku.getImages()) ? "" : StringUtils.split(sku.getImages(), ",")[0]);
            skuList.add(skuMap);
        });

        // 处理规格参数
        Map<String, Object> genericSpecs = mapper.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<String, Object>>() {
        });
        Map<String, Object> specialSpecs = mapper.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<String, Object>>() {
        });
        // 获取可搜索的规格参数
        Map<String, Object> searchSpec = new HashMap<>();

        // 过滤规格模板，把所有可搜索的信息保存到Map中
        Map<String, Object> specMap = new HashMap<>();
        params.forEach(p -> {
            if (p.getSearching()) {
                if (p.getGeneric()) {
                    String value = genericSpecs.get(p.getId().toString()).toString();
                    if(p.getNumeric()){
                        value = chooseSegment(value, p);
                    }
                    specMap.put(p.getName(), StringUtils.isBlank(value) ? "其它" : value);
                } else {
                    specMap.put(p.getName(), specialSpecs.get(p.getId().toString()));
                }
            }
        });

        goods.setId(spu.getId());
        goods.setSubTitle(spu.getSubTitle());
        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setAll(spu.getTitle() + " " + StringUtils.join(names, " "));
        goods.setPrice(prices);
        goods.setSkus(mapper.writeValueAsString(skuList));
        goods.setSpecs(specMap);
        return goods;
    }

    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result="其他";
        for (String segment  :p.getSegments().split(",") ) {
            String[] segs = segment.split("-");
            //判断是否在范围内
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length==2){
                end = NumberUtils.toDouble(segs[1]);
            }
            //判断是否在范围内
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    public PageResult<Goods> search(SearchRequest request) {
        String key = request.getKey();
        // 判断是否有搜索条件，如果没有，直接返回null。不允许搜索全部商品
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM);
        }
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 1、对key进行全文检索查询
        queryBuilder.withQuery(QueryBuilders.matchQuery("all", key).operator(Operator.AND));

        // 2、通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle
        queryBuilder.withSourceFilter(new FetchSourceFilter(
                new String[]{"id", "skus", "subTitle"}, null));

        // 3、分页
        searchWithPageAndSort(queryBuilder,request);
        //基本搜索条件
        QueryBuilder basicQuery = buildBasicQuery(request);
        //聚合

        String categoryAggName = "category"; // 商品分类聚合名称
        String brandAggName = "brand"; // 品牌聚合名称
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        //查询，获取结果
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);
        // 5、查询，获取结果
        //AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) goodsRepository.search(queryBuilder.build());
        //解析聚合结果
        Aggregations aggs = result.getAggregations();
        //解析分类聚合
        List<Category> categories =  handleCategoryAgg(aggs.get(categoryAggName));
        //解析品牌聚合
        List<Brand> brands = handleBrandAgg(aggs.get(brandAggName));

        //对规格参数聚合
        List<Map<String, Object>> specs =null;
        if (categories!=null && categories.size()==1){
            specs=handleSpecs(categories.get(0).getId(),basicQuery);
        }
        //解析分页结果
        long total = result.getTotalElements();
        int totalPage = result.getTotalPages();
        List<Goods> items = result.getContent();

        return  new SearchResult<>(total,totalPage,items,categories,brands,specs);
        //        return new PageResult<>(pageInfo.getTotalElements(), pageInfo.getTotalPages(), pageInfo.getContent());
    }
    /**
     * 对分类聚合结果进行解析
     *
     * @param terms
     * @return
     */
    private List<Category> handleCategoryAgg(LongTerms terms) {
        try {
            //获得id
            List<Long> ids = terms.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
            //根据ID查询分类
            List<Category> categories = categoryClient.queryCategoryByIds(ids);
            for (Category category : categories) {
                category.setIsParent(null);
                category.setParentId(null);
                category.setSort(null);
            }
            return categories;
        }catch (Exception e){
            logger.error("查询分类信息失败",e);
            return  null;
        }
    }
    /**
     * 对规格参数进行聚合并解析结果
     *
     * @param id
     * @param basicQuery
     * @return
     */
    private List<Map<String, Object>> handleSpecs(Long id, QueryBuilder basicQuery) {
        List<Map<String, Object>> specs = new ArrayList<>();

        //查询可过滤的规格参数
        List<SpecParam> params = specClient.querySpecParams(null, id, true, null);

        //基本查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(basicQuery);
        queryBuilder.withPageable(PageRequest.of(0, 1));

        for (SpecParam param : params) {
            //聚合
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name + ".keyword"));
        }
        //查询
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);

        //对聚合结果进行解析
        Aggregations aggs = result.getAggregations();
        for (SpecParam param : params) {
            String name = param.getName();
            Terms terms = aggs.get(name);
            //创建聚合结果
            HashMap<String, Object> map = new HashMap<>();
            map.put("k", name);
            map.put("options", terms.getBuckets()
                    .stream()
                    .map(b -> b.getKey())
                    .collect(Collectors.toList()));
            specs.add(map);
        }
        return specs;
    }
    /**
     * 解析品牌聚合结果
     *
     * @param terms
     * @return
     */
    private List<Brand> handleBrandAgg(LongTerms terms) {
        try {
            //获得品牌ID
            List<Long> ids = terms.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
            //根据品牌ids查询品牌
            return brandClient.queryBrandByIds(ids);
        }catch (Exception e){
            logger.error("查询品牌信息失败",e);
            return  null;
        }
    }


    /**
     * 构建基本查询
     *
     * @param request
     * @return
     */
    private QueryBuilder buildBasicQuery(SearchRequest request) {
        //构造布尔查询
        BoolQueryBuilder buildBasic = QueryBuilders.boolQuery();
        //搜索条件
        buildBasic.must(QueryBuilders.matchQuery("all",request.getKey()));
        Map<String, String> filterMap = request.getFilter();
        if (!CollectionUtils.isEmpty(filterMap)){
            for (Map.Entry<String,String>  entry : filterMap.entrySet()) {
                String key = entry.getKey();
                //判断key是否是分类或者商品过滤条件
                if(!"cid2".equals(key) && !"brandId".equals(key)){
                    key = "specs."+key+".keyword";
                }
                //过滤条件
                String value = entry.getValue();
                //因为是keyword类型，使用terms查询
                buildBasic.filter(QueryBuilders.termQuery(key,value));
            }
        }
        return  buildBasic;
    }

    private List<Category> getCategoryAggResult(Aggregation aggregation) {
        try {
            List<Category>  categories= new ArrayList<>();
            LongTerms categoryAgg =(LongTerms)aggregation;
            List<Long> cids = new ArrayList<>();
            for (LongTerms.Bucket bucket: categoryAgg.getBuckets()){
                cids.add(bucket.getKeyAsNumber().longValue());
            }
            //根据id查询分类名称
            List<String> names = this.categoryClient.queryNameByIds(cids);
            for (int i = 0; i <names.size() ; i++) {
                Category c = new Category();
                c.setId(cids.get(i));
                c.setName(names.get(i));
                categories.add(c);
            }
            return categories;
        } catch (Exception e) {
            logger.error("分类聚合出现异常：", e);
            return null;
        }
    }

    //构建基本查询条件
    private void searchWithPageAndSort(NativeSearchQueryBuilder queryBuilder, SearchRequest request) {
        // 准备分页参数
        int page = request.getPage();
        int size = request.getSize();
        String sortBy = request.getSortBy();
        Boolean desc = request.getDescending();
        queryBuilder.withPageable(PageRequest.of(page - 1, size));
        //2排序

        if(StringUtils.isNoneBlank(sortBy)){
            // 如果不为空，则进行排序
            queryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(desc ? SortOrder.DESC : SortOrder.ASC));

        }
    }
    /**
     * 插入或更新索引
     *
     * @param id
     */
    public  void  insertOrUpdate(Long id){
        Spu spu =goodsClient.querySpuBySpuId(id);
        if(spu==null){
            logger.error("索引对应的spu不存在，spuId:{}", id);
            throw new RuntimeException();
        }
        try{
            Goods goods = buildGoods(spu);
            goodsRepository.save(goods);
        }catch (IOException e){
            logger.error("构建商品失败", e);
            throw new RuntimeException();
        }
    }
    /**
     * 删除索引
     *
     * @param id
     */
    public void delete(Long id) {
        goodsRepository.deleteById(id);
    }

}